<?php
// 版本1.2
namespace core;

use InvalidArgumentException;
use PDOException;
use PDOStatement;
use RuntimeException;
use PDO;

class Model
{
    // 静态属性用于共享数据库连接
    protected static $db;
    protected static $transactionLevel = 0;

    // 表名
    protected $table;
    protected $name;

    // 主键（默认id）
    protected $pk = 'id';

    /**  @var array 允许的查询条件类型（防止SQL注入）*/
    protected $allowedOperators = ['=', '>', '<', '>=', '<=', '!=', 'LIKE', 'IN'];

    // 添加的字段（计算字段/自定义字段）
    protected $append = []; 

    // 隐藏的字段（需要排除的字段）
    protected $hidden = [];

    // 关联查询配置存储
    private $relationBuilders = [];

    // 预加载的关联列表
    private $with = [];

    /** @var array 存储当前查询的链式调用参数 */
    private $queryParams = [
        'conditions' => [],
        'order' => null,
        'limitOffset' => null,
        'limitCount' => null
    ];
    
    public function __construct()
    {
        // 共享数据库连接
        if (self::$db === null) {
            $config = $this->loadConfig();
            $this->initDbConnection($config);
        }
        
        // 设置表名
        $this->setTableName();
    }

    /**
     * 加载数据库配置
     * @throws \RuntimeException
     * @return array
     */
    protected function loadConfig()
    {
        $configPath = __DIR__ . '/../app/config.php';
        if (!file_exists($configPath)) {
            throw new RuntimeException("数据库配置不存在：{$configPath}");
        }

        return require $configPath;
    }

    /**
     * 初始化PDO连接
     * @param array $config
     * @throws \RuntimeException
     * @return void
     */
    protected function initDbConnection(array $config): void
    {
        try {
            $dsn = "mysql:host={$config['db_host']};dbname={$config['db_name']};charset=utf8mb4";
            self::$db = new PDO(
                $dsn,
                $config['db_user'],
                $config['db_pass'],
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false
                ]
            );
        } catch (PDOException $e) {
            throw new RuntimeException("数据库连接失败：{$e->getMessage()}", 0, $e);
        }
    }

    /**
     * 设置数据表名
     * @return void
     */
    protected function setTableName(): void
    {
        $tableName = $this->name;
        if ($tableName) {
            if (!preg_match('/^[\w]+$/', $tableName)) {
                throw new InvalidArgumentException("表名包含非法字符，仅允许字母、数字、下划线");
            }
            $this->table = $tableName;
            return;
        }

        $className = basename(str_replace('\\', '/', get_class($this)));
        $this->table = $this->camelizeToSnake($className);
    }

    /**
     * 驼峰转下划线
     * @param string $str
     * @return string
     */
    protected function camelizeToSnake(string $str): string
    {
        return strtolower(preg_replace('/(?<=\w)(?=[A-Z])/', '_', $str));
    }

    /**
     * 链式查询条件
     * @param array $conditions
     * @return self
     */
    public function where(array $conditions): self
    {
        $this->queryParams['conditions'] = array_merge(
            $this->queryParams['conditions'],
            $conditions
        );
        return $this;
    }

    /**
     * 排序
     * @param string $order 字段名
     * @param string $direction 方向（ASC/DESC）
     * @return self
     */
    public function order(string $order, string $direction = 'ASC'): self
    {
        if (!preg_match('/^[\w]+$/', $order)) {
            throw new InvalidArgumentException("排序字段包含非法字符：{$order}");
        }
        $direction = strtoupper($direction) === 'DESC' ? 'DESC' : 'ASC';
        $this->queryParams['order'] = "`{$order}` {$direction}";
        return $this;
    }

    /**
     * 分页
     * @param int $offset 偏移量
     * @param int $count 数量
     * @return self
     */
    public function limit(int $offset, int $count): self
    {
        $this->queryParams['limitOffset'] = max(0, $offset);
        $this->queryParams['limitCount'] = max(1, $count);
        return $this;
    }

    /**
     * 获取单条记录
     * @param mixed $conditions 条件（ID或数组）
     * @return array|null
     */
    public function find($conditions = null)
    {
        try {
            $this->applyConditions($conditions);
            $whereClause = $this->buildWhereClause();

            $sql = "SELECT * FROM `{$this->table}`{$whereClause['sql']}";
            
            // 处理预加载关联
            $this->appendRelationJoins($sql);
            
            // 添加排序
            if ($this->queryParams['order']) {
                $sql .= " ORDER BY {$this->queryParams['order']}";
            }

            // 添加分页
            if ($this->queryParams['limitCount'] !== null) {
                $offset = $this->queryParams['limitOffset'] ?? 0;
                $sql .= " LIMIT {$offset}, {$this->queryParams['limitCount']}";
            }

            $stmt = $this->query($sql, $whereClause['params']);
            $result = $stmt->fetch();

            // 处理结果（隐藏字段/添加字段/关联数据）
            return $result ? $this->processSingleResult($result) : null;
        } finally {
            $this->resetQueryParams();
        }
    }

    /**
     * 获取多条记录
     * @return array
     */
    public function select(): array
    {
        try {
            $whereClause = $this->buildWhereClause();

            $sql = "SELECT * FROM `{$this->table}`{$whereClause['sql']}";
            
            // 处理预加载关联
            $this->appendRelationJoins($sql);
            
            // 添加排序
            if ($this->queryParams['order']) {
                $sql .= " ORDER BY {$this->queryParams['order']}";
            }

            // 添加分页
            if ($this->queryParams['limitCount'] !== null) {
                $offset = $this->queryParams['limitOffset'] ?? 0;
                $sql .= " LIMIT {$offset}, {$this->queryParams['limitCount']}";
            }

            $stmt = $this->query($sql, $whereClause['params']);
            $result = $stmt->fetchAll();

            // 处理结果
            return $this->processMultipleResults($result);
        } finally {
            $this->resetQueryParams();
        }
    }

    /**
     * 构建WHERE子句
     * @return array [sql, params]
     */
    private function buildWhereClause(): array
    {
        if (empty($this->queryParams['conditions'])) {
            return ['sql' => '', 'params' => []];
        }

        $sqlParts = [];
        $params = [];

        foreach ($this->queryParams['conditions'] as $key => $value) {
            $operator = '=';
            $field = $key;

            if (strpos($key, ' ') !== false) {
                list($field, $operator) = explode(' ', $key, 2);
                if (!in_array(strtoupper($operator), $this->allowedOperators)) {
                    throw new InvalidArgumentException("不支持的操作符：{$operator}");
                }
            }

            if (!preg_match('/^[\w]+$/', $field)) {
                throw new InvalidArgumentException("字段名包含非法字符：{$field}");
            }

            // 处理NULL值
            if ($value === null) {
                $sqlParts[] = "{$field} IS NULL";
                continue;
            }

            // 处理IN操作符
            if (strtoupper($operator) === 'IN' && is_array($value)) {
                $placeholders = [];
                foreach ($value as $i => $val) {
                    $ph = ":{$field}_in_{$i}";
                    $placeholders[] = $ph;
                    $params[$ph] = $val;
                }
                $sqlParts[] = "`{$field}` IN (" . implode(', ', $placeholders) . ")";
            } else {
                $placeholder = ":{$field}";
                $sqlParts[] = "`{$field}` {$operator} {$placeholder}";
                $params[$placeholder] = $value;
            }
        }

        return [
            'sql' => ' WHERE ' . implode(' AND ', $sqlParts),
            'params' => $params
        ];
    }

    /**
     * 追加关联查询JOIN语句
     * @param string &$sql 主SQL语句
     */
    private function appendRelationJoins(string &$sql): void
    {
        if (empty($this->with)) {
            return;
        }

        $joins = [];
        foreach ($this->with as $relationName) {
            // 查找关联配置
            $builder = $this->findRelationBuilder($relationName);
            if (!$builder) {
                continue;
            }

            // 生成JOIN语句
            $joinType = $builder['type'] === 'belongsTo' ? 'LEFT JOIN' : 'LEFT JOIN';
            $alias = $this->getRelationAlias($relationName);
            $joinTable = (new $builder['model'])->table;
            $foreignKey = $builder['foreignKey'];
            $localKey = $builder['localKey'];

            $joins[] = "{$joinType} `{$joinTable}` AS `{$alias}` ON `{$this->table}`.`{$localKey}` = `{$alias}`.`{$foreignKey}`";
        }

        if (!empty($joins)) {
            $sql .= ' ' . implode(' ', $joins);
        }
    }

    /**
     * 查找关联配置
     * @param string $relationName 关联名称
     * @return array|null
     */
    private function findRelationBuilder(string $relationName): ?array
    {
        foreach ($this->relationBuilders as $builder) {
            if ($builder['model'] === $relationName) {
                return $builder;
            }
        }
        return null;
    }

    /**
     * 获取关联别名（避免字段冲突）
     * @param string $relationName 关联名称
     * @return string
     */
    private function getRelationAlias(string $relationName): string
    {
        return $relationName . '_rel';
    }

    /**
     * 处理单条结果（隐藏字段/添加字段/关联数据）
     * @param array $item 单条记录
     * @return array
     */
    private function processSingleResult(array $item): array
    {
        // 1. 移除隐藏字段
        foreach ($this->hidden as $field) {
            unset($item[$field]);
        }

        // 2. 添加计算字段
        foreach ($this->append as $field) {
            $item[$field] = $this->calculateAppendField($field, $item);
        }

        // 3. 合并关联数据
        foreach ($this->with as $relationName) {
            $alias = $this->getRelationAlias($relationName);
            $item[$relationName] = $item[$alias] ?? null;
            unset($item[$alias]); // 移除临时别名字段
        }

        return $item;
    }

    /**
     * 处理多条结果（隐藏字段/添加字段/关联数据）
     * @param array $items 多条记录
     * @return array
     */
    private function processMultipleResults(array $items): array
    {
        foreach ($items as &$item) {
            $item = $this->processSingleResult($item);
        }
        return $items;
    }

    /**
     * 计算添加字段的值
     * @param string $field 字段名
     * @param array $item 当前记录
     * @return mixed
     */
    private function calculateAppendField(string $field, array $item)
    {
        // 优先使用获取器方法（例如：getFullNameAttr）
        $getter = 'get' . ucfirst($field) . 'Attr';
        if (method_exists($this, $getter)) {
            return $this->$getter($item);
        }

        // 默认处理（可根据需求扩展）
        switch ($field) {
            case 'full_name':
                return $item['first_name'] . ' ' . ($item['last_name'] ?? '');
            default:
                return null;
        }
    }

    /**
     * 定义一对一关联（当前模型外键在关联模型）
     * @param string $model 关联模型类名
     * @param string $foreignKey 关联模型中的外键字段
     * @param string $localKey 当前模型的主键字段（默认id）
     * @return self
     */
    protected function belongsTo(string $model, string $foreignKey, string $localKey = 'id'): self
    {
        $this->relationBuilders[] = [
            'type' => 'belongsTo',
            'model' => $model,
            'foreignKey' => $foreignKey,
            'localKey' => $localKey
        ];
        return $this;
    }

    /**
     * 定义一对多关联（当前模型主键在关联模型）
     * @param string $model 关联模型类名
     * @param string $foreignKey 关联模型中的外键字段
     * @param string $localKey 当前模型的主键字段（默认id）
     * @return self
     */
    protected function hasMany(string $model, string $foreignKey, string $localKey = 'id'): self
    {
        $this->relationBuilders[] = [
            'type' => 'hasMany',
            'model' => $model,
            'foreignKey' => $foreignKey,
            'localKey' => $localKey
        ];
        return $this;
    }

    /**
     * 预加载关联数据
     * @param array $relations 关联名称数组
     * @return self
     */
    public function with(array $relations): self
    {
        $this->with = $relations;
        return $this;
    }

    /**
     * 插入记录
     * @param array $data
     * @return int|false
     */
    public function insert(array $data)
    {
        $keys = array_keys($data);
        $placeholders = ':' . implode(', :', $keys);
        $sql = "INSERT INTO `{$this->table}` (`" . implode('`, `', $keys) . "`) VALUES ({$placeholders})";

        $this->query($sql, $data);
        return $this->db->lastInsertId();
    }

    /**
     * 更新记录
     * @param array $data 更新数据
     * @return int 影响行数
     */
    public function update(array $data): int
    {
        $whereClause = $this->buildWhereClause();
        $setClause = $this->parseSetClause($data);
        $params = array_merge($setClause['params'], $whereClause['params']);

        $sql = "UPDATE `{$this->table}` SET {$setClause['sql']}{$whereClause['sql']}";
        $stmt = $this->query($sql, $params);

        return $stmt->rowCount();
    }

    /**
     * 解析SET子句
     * @param array $data
     * @return array [sql, params]
     */
    protected function parseSetClause(array $data): array
    {
        $set = [];
        $params = [];
        foreach ($data as $field => $value) {
            if (!preg_match('/^[\w]+$/', $field)) {
                throw new InvalidArgumentException("字段名包含非法字符：{$field}");
            }
            $placeholder = ":update_{$field}";
            $params[$placeholder] = $value;
            $set[] = "`{$field}` = {$placeholder}";
        }
        return ['sql' => implode(', ', $set), 'params' => $params];
    }

    /**
     * 删除记录
     * @param array|int $conditions 条件
     * @return int 影响行数
     */
    public function delete($conditions): int
    {
        if (is_numeric($conditions)) {
            $this->queryParams['conditions'] = [$this->pk => $conditions];
        } else {
            $this->queryParams['conditions'] = $conditions;
        }

        $whereClause = $this->buildWhereClause();
        $sql = "DELETE FROM `{$this->table}`{$whereClause['sql']}";
        $stmt = $this->query($sql, $whereClause['params']);

        return $stmt->rowCount();
    }

    /**
     * 通用查询
     * @param string $sql
     * @param array $params
     * @return PDOStatement
     */
    public function query(string $sql, array $params = []): PDOStatement
    {
        try {
            $stmt = self::$db->prepare($sql);
            $stmt->execute($params);
            return $stmt;
        } catch (PDOException $e) {
            throw new RuntimeException("查询失败: {$e->getMessage()}, SQL: {$sql}, 参数: " . json_encode($params), 0, $e);
        }
    }

    /**
     * 开启事务
     */
    public function startTrans(): void
    {
        if (self::$db && self::$transactionLevel === 0) {
            self::$db->beginTransaction();
        }
        self::$transactionLevel++;
    }

    /**
     * 提交事务
     */
    public function commit(): void
    {
        if (self::$db && self::$transactionLevel > 0) {
            self::$transactionLevel--;
            if (self::$transactionLevel === 0) {
                self::$db->commit();
            }
        }
    }

    /**
     * 回滚事务
     */
    public function rollback(): void
    {
        if (self::$db && self::$transactionLevel > 0) {
            self::$db->rollBack();
            self::$transactionLevel = 0;
        }
    }

    /**
     * 获取最后执行的SQL（调试用）
     * @return string
     */
    public function getLastSql(): string
    {
        return self::$db->query("SELECT LAST_INSERT_ID()")->fetchColumn() ?? '';
    }
}